package offer2;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo {
    /*
    LockSupport类：线程等待唤醒机制，park和unpark，阻塞线程和解除线程阻塞

    问题：我们已经有了wait/notify和await和single，为什么还需要park/unpark方法？
    答案：因为wait/notify和await和single都有限制：
    1、线程要先获得并持有锁，且必须在同步代码块中才可以。
    2、必须要先等待后唤醒，线程才能被唤醒

    LockSupport是一个线程阻塞工具类，所有的方法都是静态方法，可以让线程在任意位置阻塞，阻塞以后也有对应的唤醒方法，
    归根结底，LockSupport调用的Unsafe中的native代码

    LockSupport和每个使用它的线程都有一个许可（permit）关联，permit相当于1，0的开关，默认是0，调用一次unpark就加1变成1，
    调用一次park就会消费permit，也就是将1变成0，同时park立即返回
    如再次调用park会变成阻塞（因为permit为零了会阻塞在这里，一直到permit变为1），这时会调用unpark会把permit置位1,。
    每个线程都有一个相关的permit，permit最多只有一个，重复调用unpark也不会积累凭证

    所以我们就可以回答下面的问题了：
    问：为什么可以先唤醒线程后阻塞线程？
    答：因为unpark获得了一个凭证，之后再调用park方法，就可以名正言顺的凭证消费，而不会阻塞了

    问：为什么唤醒两次后阻塞两次，但是最终结果还是会阻塞线程？
    答：因为凭证的数量最多为1，连续调用两次unpark和调用一次unpark效果一样，只会增加一个凭证，而调用两次park却需要消费两个凭证，
    证不够，不能放行，所以被阻塞
    */

    public static void main(String[] args) {
        Thread a = new Thread(() -> {
            try {
                System.out.println("开始阻塞3秒钟");
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "\tcome in");
            LockSupport.park();
            System.out.println("被唤醒");
        }, "a");
        a.start();

        Thread b = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\tcome in");
            LockSupport.unpark(a);
            System.out.println("通知a唤醒");
        }, "b");
        b.start();
    }
}
